home *** CD-ROM | disk | FTP | other *** search
/ SGI Developer Toolbox 6.1 / SGI Developer Toolbox 6.1 - Disc 1.iso / toolbox / documents / audio / audio.apps / dev / sp.hints < prev    next >
Encoding:
Text File  |  1996-11-11  |  15.2 KB  |  334 lines

  1. <html>
  2. <title> Performance Hints for Signal-Processing Applications </title>
  3. <body>
  4. <h2> Performance Hints for Signal-Processing Applications </h2>
  5.  
  6. <p>
  7. The MIPS processor, particularly the newer ones, can execute
  8. a substantial amount of real-time signal processing code. But as with
  9. all processors, writing efficient code requires an understanding 
  10. of the processor and compiler architectures. This list is intended to 
  11. give you some things to think about.
  12.  
  13. <p>
  14. First, my <b> disclaimer. </b>
  15. I've based this list upon a number of "gotchas" that I and
  16. other audio & image-processing developers have hit. This is by 
  17. no means a complete list, but it's hopefully got some useful hints. It's
  18. a new list and I'm constantly refining it and adding things. If you 
  19. have your own "gotchas" or suggestions, please send them in!
  20. <p>
  21. Thanks to Bruce Karsh, Gints Klimanis, Chris Pirazzi, and Bryan James
  22. for their review and contributions.
  23. <p>
  24. <a href="http://reality.sgi.com/employees/cook/"> Doug Cook </a><br>
  25. <a href="mailto:cook@sgi.com"> (cook@sgi.com) </a> 
  26. <hr>
  27. 1. <b> There are few "magic bullets", only guidelines and trade-offs. </b> What 
  28. speeds up one app may slow-down another if the applications are architected 
  29. differently.  Take the time to understand and profile your application to 
  30. confirm that these guidelines help or don't help.
  31.  
  32. <hr>
  33. 2. <b> Consider the data type you need for each operation. </b> Don't make the 
  34. assumption that integer types are faster than floating-point. This is 
  35. often not true. Here are some gross generalizations about what's best
  36. for different types of operations:
  37. <pre>
  38.     Bit-Swizzling & Logical Ops    Integer only
  39.     Addition            Integer faster than FP
  40.     General Multiplication        FP faster than integer
  41.     Multiplication by a Constant    Integer is often faster if you can 
  42.                     write it as a small number of shifts 
  43.                     & adds. Otherwise FP is faster.
  44.     Division            FP is faster. But if you can replace
  45.                     division by multiplication, do so; 
  46.                     multiplication is faster.
  47.     Conditionals            Integer is faster
  48. </pre>
  49.  
  50. Another thing to consider is that the FP unit has its own register set.
  51. When you use FP, you effectively get a much larger register set to use, because
  52. your logical code uses the integer registers, whereas the computations use
  53. the FP registers. With more registers, some loops may require fewer
  54. load and store operations. 
  55. <p>
  56.  
  57. In general, for the typical signal-processing application, which 
  58. requires a lot of multiply/adds of variables, floating-point offers superior 
  59. performance. But as always, it depends upon the architecture of the
  60. application; your mileage may vary.
  61.  
  62. <p>
  63. 2a. Another note: one common speed-up for integer operations is to use
  64. table-lookup, where possible. This often works well for image processing,
  65. where the input is 8- or 10-bit data and the tables are small enough to
  66. stay in the cache. For audio operations, table-lookup is less common, since
  67. audio data is typically 16-24 bits.
  68.  
  69. <hr>
  70. 3. <b> Avoid unnecessary type conversion. </b> This is a trade-off with #1.
  71. Conversion between integer and floating-point can be expensive
  72. (particularly FP->int, which sometimes requires limiting). If your
  73. application accepts and produces integer data and only does a small
  74. amount of signal-processing, the cost of conversion to FP may outweigh
  75. the gains in performance from using FP. 
  76.  
  77. <p>
  78. Also, be aware: in some cases, the compiler may do type conversion 
  79. "behind your back," particularly by promoting single-precision to 
  80. double-precision floating-point (hint #7).
  81.  
  82. <hr>
  83. 4. <b> Consider the instruction set (ISA) you want to use. </b>
  84. If you're writing in C, selecting an instruction set requires just a 
  85. compiler flag,
  86. e.g. -mips2. There are currently 4 MIPS instruction sets (MIPS 1-4). 
  87. Each set is a superset of the previous. Roughly speaking, here's how they 
  88. work:
  89. <pre>
  90.     MIPS 1        R3000
  91.     MIPS 2        R4000 instructions
  92.     MIPS 3        R4000 -- introduces 64-bit operations
  93.     MIPS 4        R8000/R10000 instructions
  94.             notably floating-point multiply/add, conditional FP move
  95. </pre>
  96.  
  97. <p>
  98. I've noticed that one big benefit of going from MIPS1->MIPS2 is a
  99. reduction in the cost of FP->integer conversion. For MIPS1, the
  100. compiler will generate some inline code to perform this; for MIPS2, the
  101. code is replaced by a single instruction. In general, though, you
  102. should not expect a huge performance gain going mips1->mips2.
  103.  
  104. <p>
  105. The MIPS4 instruction set provides some nice opportunities for
  106. signal-processing applications. It provides an FP multiply/add
  107. instruction and floating-point conditional-move instructions. These
  108. last allow one to write branch-free limiting code, which is a big
  109. win in some cases (see #5).
  110.  
  111. <p>
  112. With each instruction set, you may get a performance gain at the cost
  113. of compatibility with older MIPS-architecture processors. In other
  114. words, if you compile -mips2, you may see some speedup, but you lose
  115. R3000 compatibility. This is a trade-off you will have to consider.
  116. Some people maintain multiple versions of a binary using different
  117. ISA's. (If your application uses "inst" for installation, you can have
  118. "inst" automatically select the appropriate binary for the end-user's
  119. machine).
  120.  
  121. <hr>
  122. 4. <b> Disassemble your code and look at it. </b> Use the "dis" command on your
  123. actual binary, not the code generated by "cc -S" (the
  124. compiler lies, often substituting higher-level macros for more complex
  125. things that actually occur in the binary itself).
  126.  
  127. <p>
  128. There's really no substitute for this. Looking at the source-code
  129. and thinking about the algorithms at a high-level is a first step, but
  130. in the end if you need to squeeze every bit of performance out of 
  131. your code, you need to examine the actual instructions that the CPU
  132. will execute. 
  133.  
  134. <hr>
  135. 5. <b> Avoid branches where possible in critical code. </b> If you can replace a
  136. bit of frequently executed code by an equivalent branch-free piece of 
  137. code, it may run in fewer cycles though it has more instructions. This is
  138. because the MIPS processors are pipelined, and for some of them, a branch 
  139. requires several extra cycles to refill the pipeline.
  140.  
  141. <p>
  142. Another reason to avoid branches is that many of the compiler optimizations
  143. are limited to basic blocks, i.e. the region between two branches. Removing
  144. branches often gives the compiler a better chance to optimize the code.
  145. Finally, branches inside loops reduce the effectiveness of loop unrolling.
  146.  
  147. <hr>
  148. 6. <b> Work with the cache. </b> Though the processor is very fast, the memory
  149. system is relatively slow. If you don't get good cache performance,
  150. your application will suck wind. A good rule of thumb is the
  151. order-of-magnitude rule. Think of each successive level of cache, going
  152. from the processor out to main memory, as 10 times as slow as the previous 
  153. level. Suppose an instruction which hits the primary cache takes 1 cycle.  
  154. The same instruction may require 10 cycles if it misses the primary cache 
  155. and hits the secondary cache, and 100 cycles if it misses the secondary cache 
  156. and requires a cache-line fetch from main memory. The effect is more
  157. pronounced as processors become faster. Cache effects can thus make a 
  158. huge difference in program performance.
  159.  
  160. <p>
  161. Organize your code and your data to optimize your use of the cache.
  162. Minimize your inner loops and working sets of data. For example, in
  163. image-processing, "tiling" is a common practice. Suppose a number of
  164. consecutive operations are to be performed on an image. The "obvious" way
  165. would be to perform each operation on the entire image, then proceed to
  166. the next operation. However, assuming the image doesn't fit in the cache,
  167. this gets the worst possible cache performance, since the image has to
  168. be reloaded from main memory every time it is touched. A far superior 
  169. approach is to operate on small "tiles" which fit in the cache: perform
  170. all the operations on one tile, then proceed to the next.
  171.  
  172. <p>
  173. There are lots of other tricks to improve cache performance, but hopefully
  174. this short example will get you thinking. If you need data in a critical
  175. loop, try to make sure it's in the cache every time the loop is executed.
  176.  
  177. <hr>
  178. 7. <b> Watch out for floating-point promotion. </b> Double-precision arithmetic
  179. is typically more costly than single-precision, so if you don't need
  180. it, make sure you're not using it. In some cases the compiler will
  181. automatically promote operands to double-precision "behind your back."
  182. Here are some things to look for. Check out the "-float" option on the
  183. C compiler, which instructs the compiler (in K&R mode) to avoid
  184. promoting operands to double-precision. It's also a very good idea to
  185. use function prototypes with functions taking "float" arguments --
  186. otherwise the arguments will be promoted to double-precision (even with
  187. the -float option). Finally, if a floating-point constant is to be
  188. single-precision, it should be given in the form "0.0f", i.e.
  189. explicitly single-precision, to avoid promotion to double-precision.
  190.  
  191. <hr>
  192. 8. <b> Don't assume that assembly language is inherently better than
  193. C code. </b> Write your algorithm in C unless you find by disassembly
  194. that the compiler is missing some possible optimizations, or
  195. unless there's some special trick you need that can really only
  196. be expressed in assembly. In most cases you'll find that the compiler 
  197. does a really good job.
  198.  
  199. <hr>
  200. 9. <b> Don't assume that pointer arithmetic is inherently better than
  201. array indexing. </b> The normal addressing mode of the MIPS processor (a fixed 
  202. offset from a register) lends itself nicely to array indexing. Consider
  203. the following two loops:
  204. <pre>
  205.     for (i=0; i < 50; i++) {
  206.         array[i]=0;
  207.     }
  208.  
  209.     int *p=array;
  210.     for (i=0; i< 50; i++) {
  211.         *p++=0;
  212.     }
  213. </pre>
  214. <p>
  215. Both are functionally equivalent if the value of p is not used later 
  216. in the second case. Some people shy away from the first approach on 
  217. the mistaken belief that the address generation for the array indexing 
  218. is computationally expensive. In fact, the compiler generates code for 
  219. the first version which has 20% fewer instructions per iteration than the 
  220. code for the second version. Why? The compiler often generates better code 
  221. for simpler expressions, because it can better understand what the code 
  222. is trying to accomplish.  In the first case, the compiler sees that the code 
  223. is looping on an array index. It calculates the end address of the array
  224. (200 from the base, since it is an integer array), and generates a loop which 
  225. has only one variable -- the address in the array. "i" as such is nowhere to 
  226. be seen. The loop requires four instructions per iteration:
  227. <pre>
  228.   [test1.c:   5] 0x4009a8:      248500c8        addiu   a1,a0,200
  229.   [test1.c:   5] 0x4009ac:      24840004        addiu   a0,a0,4
  230.   [test1.c:   5] 0x4009b0:      0085082b        sltu    at,a0,a1
  231.   [test1.c:   5] 0x4009b4:      1420fffd        bne     at,zero,0x4009ac
  232.   [test1.c:   6] 0x4009b8:      ac80fffc        sw      zero,-4(a0)
  233. </pre>
  234. <p>
  235. The second example is too "smart" for the compiler. 
  236. The compiler can't quite figure out what the programmer is trying
  237. to accomplish, so it generates two loop variables, one for "i" and
  238. one for "p" (even though "p" is never used after the loop). This
  239. example requires 5 instructions per iteration, since it has to
  240. increment two loop variables.
  241. <pre>
  242.   [test2.c:   4] 0x4009a8:      24020032        li      v0,50
  243.   [test2.c:   6] 0x4009ac:      00002025        move    a0,zero
  244.   [test2.c:   6] 0x4009b0:      24840001        addiu   a0,a0,1
  245.   [test2.c:   6] 0x4009b4:      0082082a        slt     at,a0,v0
  246.   [test2.c:   7] 0x4009b8:      ac600000        sw      zero,0(v1)
  247.   [test2.c:   6] 0x4009bc:      1420fffc        bne     at,zero,0x4009b0
  248.   [test2.c:   7] 0x4009c0:      24630004        addiu   v1,v1,4
  249. </pre>
  250. <p>
  251. (if you're not familiar with MIPS assembly, note that the instruction
  252. after the branch [bne] is executed before the branch actually occurs.
  253. This is the so-called "branch delay slot").
  254. <p>
  255. Some folks have tested numerous ways of stating loops to see for
  256. which the compiler generates the fastest code. This is an interesting
  257. experiment, but it may not hold true from compiler release to compiler
  258. release. However, the general principle holds: don't be afraid to state your 
  259. loops in simple terms. Often the compiler does best with the simplest 
  260. constructs.
  261. <hr>
  262. 10. <b> Watch out for expressions hidden in macros. </b> Know what's a macro
  263. and what's a function. Expressions hidden in macros will sometimes
  264. be evaluated multiple times; this causes a performance hit if it's
  265. not required for correct evaluation of the macro.
  266. <hr>
  267. 11. <b> Consider unrolling your loops. </b> Often the compiler will do this 
  268. for you, but you can sometimes glean a little more performance by doing
  269. it yourself. Loop unrolling works as follows. Consider the following loop:
  270. <pre>
  271.     for (i=0; i < x; i++) {
  272.         array[i]=0;
  273.     }
  274. </pre>
  275. <p>
  276. We've seen above (#9) that the compiler generates somewhat smart code
  277. for this. For each iteration of the loop, the pointer is incremented
  278. and tested against its end value. But we can cut down on the number of 
  279. instructions per array item if we write the loop like this:
  280. <pre>
  281.     for (i=0; i < x; i+=4) {
  282.         array[i]=0;
  283.         array[i+1]=0;
  284.         array[i+2]=0;
  285.         array[i+3]=0;
  286.     }
  287. </pre>
  288. <p>
  289. This loop is faster than the first one. The trick is that the pointer
  290. increment and test-against-end-value need only be done for every 4
  291. array elements, instead of for every array element.  There's no extra
  292. computation overhead associated with the (i+n) indices, because these
  293. make use of the MIPS addressing mode: the compiler can determine the
  294. fixed offset (represented by n) from the base address represented by
  295. i. Also, since there are fewer iterations, there are fewer branches.
  296. As noted above, a pipelined processor likes to execute instructions
  297. linearly (#5). The compiler-generated code looks like:
  298. <pre>
  299.   [test3.c:   6] 0x4009a8:      248500c8        addiu   a1,a0,200
  300.   [test3.c:   6] 0x4009ac:      24840010        addiu   a0,a0,16
  301.   [test3.c:   6] 0x4009b0:      0085082b        sltu    at,a0,a1
  302.   [test3.c:   7] 0x4009b4:      ac80fff0        sw      zero,-16(a0)
  303.   [test3.c:   8] 0x4009b8:      ac80fff4        sw      zero,-12(a0)
  304.   [test3.c:   9] 0x4009bc:      ac80fff8        sw      zero,-8(a0)
  305.   [test3.c:   6] 0x4009c0:      1420fffa        bne     at,zero,0x4009ac
  306.   [test3.c:  10] 0x4009c4:      ac80fffc        sw      zero,-4(a0)
  307. </pre>
  308. <p>
  309. Note that this snippet assumes that x is divisible by 4, unlike the
  310. original loop. There's a way around this for general values of x:
  311. split the loop into two loops. The first is not unrolled, and processes
  312. (x & 3) elements. The remaining number of elements is guaranteed to be
  313. divisible by 4, and the second, unrolled loop handles the bulk of the
  314. elements.  If x is going to be very small, it's not worth the overhead,
  315. but for most values of x, this approach is a big win.
  316. <p>
  317. So why did we choose 4? First, it's a power of two, which makes the 
  318. modulo operation cheap (x & 3, as seen above). It's also fairly small.
  319. As the loop-unrolling block size becomes larger, you get diminishing
  320. returns. At some point, things actually slow down because the code
  321. no longer gets nice cache behavior. Common values for the block size
  322. are 4, 8, and sometimes 16. 
  323. <p>
  324. Experiment with your code to see what works well. As always, if it's
  325. a critical loop, disassemble it to see what the compiler is doing for
  326. you.
  327. <hr>
  328. 12. <b> Watch out for floating-point exceptions. </b> These can really hurt
  329. your performance. Check out the <a href="fp.underflow"> excellent little blurb </a> that Chris
  330. Pirazzi has written, available from the audio apps page.
  331. <p>
  332. [...more to come...]
  333. </body>
  334.